package com.colladaviewer.android.camera;

import java.io.ByteArrayOutputStream;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.YuvImage;
import android.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.PreviewCallback;
import android.hardware.Camera.Size;
import android.view.SurfaceHolder;



/**
 * A CameraSource implementation that obtains its bitmaps directly from the
 * device camera.
 * 
 * @author Tom Gibara
 *
 */

public class GenuineCamera implements CameraSource 
{
    private final static Logger logger = Logger.getLogger(AndroidARManager.class.getName());
	
    private volatile int width;
	private volatile int height;
	private volatile boolean camIsPreviewing;
	
	private CamFetcher camFetcherThread;
	private SurfaceHolder surfaceHolder;
	 
	
	public GenuineCamera(SurfaceHolder surfaceHolder, int width, int height) 
	{
		this.width = width;
		this.height = height;
		this.surfaceHolder = surfaceHolder;
		camIsPreviewing = false;
		camFetcherThread = null;
	}
	
	public int getWidth() {
		return width;
	}
	
	public int getHeight() {
		return height;
	}
    
	/**
     * Returns an exception or null if no error
     * @return
     */
    public synchronized Throwable getException()
    {
        return camFetcherThread == null ? null : camFetcherThread.getException(); 
        
    }
    
	public boolean open() 
	{
	    if (camFetcherThread == null)
	    {
	        camFetcherThread = new CamFetcher();
	        camFetcherThread.start();
	    }
	    	    
        try {
            int i = 0;
            // Wait for camera to open and start preview
            while ((!camIsPreviewing) && (i++ < 50)) // 5 seconds
            {
                Thread.sleep(100);
            }
        } catch (InterruptedException e) {
        }          
	    
		return camIsPreviewing;
	}
	

	public void close() 
	{
	    if (camFetcherThread != null)
	    {
	        camFetcherThread.stop();
	        camFetcherThread = null;
	    }
	}
	
	public Bitmap getLastBitmap()
	{          
	    Bitmap bitmap = null;
	    if (camFetcherThread != null)
	    {
	        bitmap = camFetcherThread.getBitmap();
	    }	     
	    return bitmap;
	}
	
	
	/**
	 * This thread fetches and converts preview images from the hardware cam
	 * @author as
	 *
	 */
	private class CamFetcher implements PreviewCallback, Runnable
	{
        private volatile Thread thread = null;
        private volatile boolean stop;
        
	    private Camera camera;
	    
	    // The buffer that gets filled by the camera preview callback
	    private byte[] buffer;
	    private volatile boolean newBufferAvailable = false;
	    
	    private Bitmap bitmap;
	    private volatile boolean bitmapAvailable = false;
	    
	    private volatile Throwable exception = null;
	    
	    public CamFetcher()
	    {
	        stop = false;  
	    }
	    
	    /**
	     * new frame from the camera arrived
	     */
	    public synchronized void onPreviewFrame(byte[] data, Camera camera) 
	    {
            //prevent null pointer exceptions
            if (data == null)
                return;
    
            buffer = data;
            newBufferAvailable = true;	            
	    }
	    
	    public synchronized Bitmap getBitmap()
	    {
	        Bitmap result = null;
	        if (bitmapAvailable)
	        {
	            result = bitmap;
	            bitmapAvailable = false;
	        }
	        return result;
	    }
	    
	    /**
	     * Returns an exception or null if no error
	     * @return
	     */    
	    public synchronized Throwable getException()
	    {
	        return exception; 
	        
	    }
	    
        public synchronized void start()
        {
            if (thread == null)
            {
                thread = new Thread(this);
                thread.setDaemon(true);
                thread.setName("CamFetcher");
                thread.start();
            }
        }
          
        public synchronized void stop()
        {
            if (thread != null)
            {
                stop = true;
                interrupt();  
                try {
                    thread.join(5000);
                } catch (InterruptedException e) 
                {
                    logger.severe("Failed to stop CamFetcher thread: " + e.getMessage());
                }                
                thread = null;                
            }
              
        }
          
        public synchronized void interrupt()
        {
            if (thread != null)
            {
                thread.interrupt();
            }
        } 
                
        public void run()        
        {
            try
            {
                camera = android.hardware.Camera.open();
                //camera = CameraHolder.instance().open();
                camera.setPreviewDisplay(surfaceHolder);
                
                setPreviewFormat(camera);

                Parameters camParams = camera.getParameters();                 
                int format = camParams.getPreviewFormat();                
                int bitsPerPixel = ImageFormat.getBitsPerPixel(format);
                
                  //get width/height of the camera
                Size previewSize = camParams.getPreviewSize();
                    
                // Allocate a new buffers for frame callback
                int bufferSize = ((previewSize.width * previewSize.height) * bitsPerPixel) / 8;
                buffer = new byte[bufferSize];
                camera.addCallbackBuffer(buffer);                
                camera.setPreviewCallbackWithBuffer(this);
                
                                    
                width = previewSize.width;
                height = previewSize.height;
                
                                                                                 
                camera.startPreview();
                camIsPreviewing = true;

                
                //final YuvImage yuvimage = new YuvImage(buffer,ImageFormat.NV21,width,height,null);
                final ByteArrayOutputStream baos=new ByteArrayOutputStream();            
                final Rect rect = new Rect(0,0,width,height);
                final Matrix flipMat = new Matrix();
                flipMat.preScale(1.0f, -1.0f);
                byte[] jpeg;
    
                while (! stop) 
                {
                    if (newBufferAvailable)
                    {
                        if (!bitmapAvailable)
                        {
                            // Bitmap was fetched
                            // create next one

                            // Get next picture from the camera
                            synchronized(this)
                            {
                                YuvImage yuvimage = new YuvImage(buffer,ImageFormat.NV21,width,height,null);
                                //ByteArrayOutputStream baos=new ByteArrayOutputStream();
                                
                                yuvimage.compressToJpeg(rect, 80, baos);
                                jpeg = baos.toByteArray();
                                
                                                                                                                                            
                                //bitmap = BitmapFactory.decodeByteArray(jpeg, 0, jpeg.length, null);
                                // Flip the texture
                                bitmap = Bitmap.createBitmap(BitmapFactory.decodeByteArray(jpeg, 0, jpeg.length, null),
                                                             0, 0, width, height, flipMat, false);                        
                                baos.reset();
                                                                               
                                bitmapAvailable = true;
                                newBufferAvailable = false;
                                
                                // Request next preview image into buffer
                                camera.addCallbackBuffer(buffer);
                            }
                        }
                        else
                        {
                            try { Thread.sleep(50); }
                            catch (InterruptedException e) { }  
                        }
                    }
                    else
                    {
                            try { Thread.sleep(100); }
                            catch (InterruptedException e) { }                                      
                    }
                    
                    
                } // end while (! stop)
            }
            catch (Exception e)
            {
                logger.severe("CamFetcher failed: " + e.getMessage());
                exception = e;
            }
            finally
            {
                if (camera != null)
                {
                    if (camIsPreviewing)
                    {
                        camera.stopPreview();
                        camIsPreviewing = false;                                                                
                    }
                    camera.release();
                    //CameraHolder.instance().release();
                }
                logger.info("CamFetcher thread terminated");
            }
        }       	    	    
	}
	
	private void setPreviewFormat(Camera camera)
	{
	    Parameters camParams = camera.getParameters();
	    
        if (camParams.getPreviewFormat() != ImageFormat.NV21) 
        {
            // try to set the preview format, if it was not YCbCr_420 already
            camParams.setPreviewFormat(ImageFormat.NV21);                   
            camera.setParameters(camParams);
            camParams = camera.getParameters();
        }

        List<Size> sizes = camParams.getSupportedPreviewSizes();
        Size optimalSize = null;

        for (Iterator<Size> iterator = sizes.iterator(); iterator.hasNext();) 
        {
            Size currSize =  iterator.next();
            if(optimalSize != null) 
            {
                //is the current size smaller than the one before
                if(optimalSize.height > currSize.height && optimalSize.width > currSize.width) 
                {
                    optimalSize = currSize;
                } 
            }
            else 
            {
                optimalSize = currSize;
            }
            
        }
        if (camParams.getPreviewSize().width != optimalSize.width)
        {
            camParams.setPreviewSize(optimalSize.width, optimalSize.height);
            
        }
        camera.setParameters(camParams);
	}

}
